package analizadorLexico;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Hashtable;


public class AnalizadorLexico {
	//Definici�n de los nombres de Tokens.
		private static final String 
		TK_IDENTIFIER	="TK_identifier",TK_NUMERO	="TK_numero",	TK_IF		="TK_if",
		TK_ELSE			="TK_else",		TK_CLASS 	="TK_class", 	TK_EXTENDS  ="TK_extends",
		TK_NEW			="TK_new",		TK_RETURN	="TK_return", 	TK_SUPER 	="TK_super",
		TK_THIS			="TK_this",		TK_WHILE	="TK_while",	TK_BOOLEAN  ="TK_boolean",
		TK_CHAR 		="TK_char",	 	TK_INT 		="TK_int",		TK_FOR 		 ="TK_for",
		TK_VOID 		="TK_void",	 	TK_CLASSDEF	 ="TK_classDef",
		TK_TAB 			="TK_tab",		TK_NEWLINE 	="TK_newline",	TK_STRING	 ="TK_string",
		TK_TRUE 		="TK_true", 	TK_FALSE 	="TK_false", 	TK_NULL		 ="TK_null",
		TK_MOD 			="TK_mod", 		TK_OR 		="TK_or", 		TK_AND 		 ="TK_and",
		TK_MAS			="TK_mas",		TK_MENOS 	="TK_menos", 	TK_POR 		 ="TK_por",
		TK_DIV 			="TK_div",		TK_COMA		="TK_coma", 	TK_PUNTO	 ="TK_punto",
		TK_DIGUAL 		="TK_digual",	TK_IGUAL 	="TK_igual",	TK_MAYIG	 ="TK_mayig",
		TK_MENIG 		="TK_menig",	TK_DIST 	="TK_dist", 	TK_MEN 		 ="TK_men",
		TK_MAY 			="TK_may", 		TK_NOT 		="TK_not",		TK_EOF 		 ="TK_eof",
		TK_CAD	 ="TK_cadena",
		TK_PARENTESISABRE ="TK_parentesisAbre",TK_PARENTESISCIERRA="TK_parentesisCierra",
		TK_LLAVEABRE 	  ="TK_llaveAbre", 	   TK_LLAVECIERRA 	  ="TK_llaveCierra", 
		TK_PUNTOYCOMA 	  ="TK_puntoycoma",	   TK_CHARLITERAL = "TK_charLiteral"; 
	//-----------------------------------------------------------------------------------------
		int linea=1;
		BufferedInputStream stream; 
		Hashtable<String, String> palabrasReservadas;
		Hashtable<String, String> palabrasProhibidas;
		
			/*int b;
			long checkSum = 0L;
			while ( (b=f.read()) != -1 )
			    checkSum += b;*/
	
	

	//-----------------------------------------------------------------------------------------
		
	/*
	 * Constructor del analizador.
	 * Recibe el (path del) archivo fuente, arrojando una excepci�n si no lo encuentro.
	 * inicializa el buffer, y las tablas de palabras reservadas y prohibidas	
	 */
	public AnalizadorLexico(String file) throws FileNotFoundException{
		stream = new BufferedInputStream(
			    new FileInputStream(file));
		palabrasReservadas  = new Hashtable<String,String>();
		palabrasProhibidas = new Hashtable<String,String>();
		inicializarPalabraReservada();
		inicializarPalabraProhibidas();
	}
	/*
	 * M�todo principal del analizador. simula un aut�mata infinito para devolver
	 * un token, leyendo desde el archivo fuente.
	 */
	public Token getToken() throws IOException, LexicalException{
		int estado = 0; //estado inicial
		char charActual; //car�cter actualemente leido
		String lexema = ""; //el lexema, inicializado como "vac�o"
		//loop principal. Solo termina cuando se encuentra error (lanza excepci�n)
		//o cuando se devuelve un token (finaliza la funci�n)
		while(true){
			
			//primero, se lee el caracter, para chequear que no sea EOF
			int character= stream.read();
			if (character != -1)
				charActual=(char)character;
			else//si es EOF, lo asumimos nulo.
				charActual =0;
			
			//switch-case que simula el recorrido del aut�mata finito
			//(se tomaron algunas decisiones que alejan al algoritmo de la simulaci�n
			//del aut�mata, en pos de la eficiencia y facilidad de lectura)
			switch(estado){
			case 0:		//estado inicial, y aceptador para tokens espec�ficos			
				if (esNum(charActual)){
					estado = 1;					
					lexema =lexema+charActual;
					stream.mark(1);
					break;
				}
				if (charActual == '\\'){
					throw new LexicalException("Caracter inv�lido! linea "+linea);
				}
				if (esLetra(charActual) || charActual== '_'){
					estado = 2;
					lexema =lexema+charActual;
					stream.mark(1);
					break;
				}
				if (charActual=='\''){
					estado=3;
					break;
				}
				if (charActual == '\n'){
					estado = 0;
					linea++;
					break;
				}
				if (charActual== '/'){
					stream.mark(1);
					estado=10;
					break;
				}
				if (charActual == ','){
					return new Token(TK_COMA,",",linea);
				}
				if (charActual == ';'){
					return new Token(TK_PUNTOYCOMA,";",linea);					
				}
				if (charActual == '.'){
					return new Token(TK_PUNTO,".",linea);					
				}
				if (charActual == '('){					
					return new Token(TK_PARENTESISABRE,"(",linea);					
				}
				if (charActual == ')'){
					return new Token(TK_PARENTESISCIERRA,")",linea);					
				}
				if (charActual == '{'){
					return new Token(TK_LLAVEABRE,"{",linea);					
				}
				if (charActual == '}'){					
					return new Token(TK_LLAVECIERRA,"}",linea);					
				}
				if (charActual=='\"'){
					estado=8;
					break;
				}
				if (charActual=='='){
					stream.mark(1);
					estado=14;
					break;
				}
				if (charActual=='>'){
					stream.mark(1);
					estado=15;
					break;
				}
				if (charActual=='<'){
					estado=16;
					stream.mark(1);
					break;
				}
				if(charActual=='&'){
					estado=17;
					break;
				}
				if(charActual=='|'){
					estado=18;
					break;
				}
				if(charActual=='!'){
					stream.mark(1);
					estado=19;
					break;
				}
				if(charActual=='+'){
					estado=20;
					stream.mark(1);
					break;
				}
				if(charActual=='-'){
					estado=21;
					stream.mark(1);
					break;
				}
				if(charActual=='*'){
					estado=22;
					stream.mark(1);
					break;
				}				
				if(charActual=='%'){
					estado=24;
					stream.mark(1);
					break;
				}
				if(esCharExtraño(charActual)){
					throw new LexicalException("Caracter extra�o! linea "+linea);	
				}
				if (character == -1){
					estado = 0;
					return new Token(TK_EOF,"EOF",linea);					
				}
				break;
				
			case 1: //estado 1 = acepta el token "TK_numero"
			
				if (esNum(charActual)){
					lexema =lexema+charActual;
					stream.mark(1); //marcamos por si es el �ltimo d�gito (v�lido por los pr�ximos 5 bytes)
					break;
				}
				else{
					if (esLetra(charActual)){
						throw new LexicalException("ERROR LEXICO!:N�mero mal formado en linea "+linea);
					}
					Token t = new Token(TK_NUMERO,lexema,linea);
					stream.reset();
					estado = 0;
					return t;
					
				}				
				
				
			case 2://estado 2 = acepta los identificadores y palabras reservadas, chequeando tambi�n
				   //las palabras prohibidas.
				if((esLetra(charActual)) || (esNum(charActual) || (charActual== '_'))){
					lexema =lexema+charActual;
					stream.mark(1); //marco la posicion en el stream, por si es el ultimo caracter del identificador
					break;
				}
				else{
					stream.reset(); //vuelvo el pointer en el stream a la posicion adecuada.
					//ver si identificador es reservada o prohibida
					String s= palabrasReservadas.get(lexema);
					if (s!=null){
						Token t = new Token(s,lexema,linea);
						return t;
					}
					else{
						s=palabrasProhibidas.get(lexema);
						if(s!=null){
							throw new LexicalException("ERROR!:Palabra prohibida "+lexema+" "+linea);
						}
						else{
							Token t = new Token(TK_IDENTIFIER,lexema,linea);
							estado = 0;
							return t;
						}
					}
				}
			//estados 3,4,5,6,7 aceptan los literales char correspondientes
			case 3:
				if (esLetra(charActual)){
					lexema=lexema+charActual;
					estado=4;
					break;
				}
				else {
					if (charActual=='\\'){
						estado=5;
						break;
					}
					if (charActual=='\'')
						throw new LexicalException("ERROR!:Caracter mal formado en linea "+linea);
				}
			case 4:
				if (charActual== '\''){
					Token t = new Token(TK_CHARLITERAL,lexema,linea);
					estado = 0;
					return t;
				}
				else{
					throw new LexicalException("ERROR!:Caracter mal formado en linea "+linea);
				}
			case 5:
				if (esLetra(charActual)){					
					if (charActual != 'n'&& charActual !='t'){
					estado=4;
					lexema=lexema+charActual;					
					break;
					}
					else{
						if (charActual == 'n'){
							estado=6;
							lexema = "\\n";
							break;
						}
						if (charActual == 't'){
							estado=7;
							lexema = "\\t";
							break;
						}
					}
				}
			case 6:
				if (charActual== '\''){
					Token t = new Token(TK_NEWLINE,lexema,linea);
					estado = 0;
					return t;
				}
				else{
					throw new LexicalException("ERROR!:Caracter mal formado en linea "+linea);
				}
			case 7:
				if (charActual== '\''){
					Token t = new Token(TK_TAB,lexema,linea);
					estado = 0;
					return t;
				}
				else{
					throw new LexicalException("ERROR!:Caracter mal formado en linea "+linea);
				}
			
			//estados 8 y 9 aceptan las cadenas de string correspondientes a DeCaf
			case 8:
				if(charActual=='\\'){
					estado = 9;
					break;
				}
				else
					if(charActual == '\"'){
						Token t = new Token(TK_CAD,lexema,linea);
						estado = 0;
						return t;
					}
					if(character == -1){
						throw new LexicalException("String sin cerrar! linea "+linea);
					}
					else{
						lexema = lexema+charActual;
						break;
					}
				
			case 9:
				if (charActual == 'n'){
					estado = 8;
					break;
				}
				else{
					lexema = lexema+"\\"+charActual;
					estado=8;
					break;
				}
			case 10:
				if (charActual== '*'){
					estado = 11;
					break;
				}
				if(charActual=='/'){
					estado= 13;
					break;
				}
				if (charActual=='=')
					throw new LexicalException("Caracter extra�o  ! linea "+linea);
				else{
					stream.reset();
					Token t = new Token(TK_DIV,"/",linea);
					estado = 0;					
					return t;
				}
			case 11:
				if(character == -1){
					throw new LexicalException("Comentario sin cerrar! linea "+linea);
				}
				if(charActual == '\n')
					linea++;
				if (charActual== '*'){
					estado =12;
					break;
				}
			case 12:
				
					//Se leyo un comentario correctamente, se ignora y se vuelve al estado inicial
					// para reconocer el proximo token
					if(charActual == '/'){
						estado = 0;
						break;	
				    }
					else{
						//No se cierra el comentario se vuelve a esperar el cierre del comentario
						estado=11;
						break;
					}
			case 13:
			
				if (charActual == '\n' || character == -1){
					estado=0;
					linea++;
					break;
				}
				else
					break;
		
			case 14:	
				if (charActual=='='){
					Token t = new Token(TK_DIGUAL,"==",linea);
					estado = 0;
					return t;
				}
				else{
					Token t = new Token(TK_IGUAL,"=",linea);
					stream.reset();
					estado = 0;
					return t;
				}
			case 15:
				if (charActual=='='){
					Token t = new Token(TK_MAYIG,">=",linea);
					estado = 0;
					return t;
				}
				if (charActual=='>'){
					throw new LexicalException("Caracter extra�o! linea "+linea);
				}
				else{
					stream.reset();
					Token t = new Token(TK_MAY,">",linea);
					estado = 0;
					return t;
				}
		
			case 16:
				if (charActual=='='){
					Token t = new Token(TK_MENIG,"<=",linea);
					estado = 0;
					return t;
				}
				if (charActual=='<'){
					throw new LexicalException("Caracter extra�o! linea "+linea);
				}
				else{
					stream.reset();
					Token t = new Token(TK_MEN,"<",linea);
					estado = 0;
					return t;
				}
			case 17:
				if (charActual=='&'){
					Token t = new Token(TK_AND,"&&",linea);
					estado = 0;
					return t;
				}
				else{
					throw new LexicalException("Caracter extra�o & ! linea "+linea);
				}
			case 18:
				if (charActual=='|'){
					Token t = new Token(TK_OR,"||",linea);
					estado = 0;
					return t;
				}
				else{
					throw new LexicalException("Caracter extra�o | ! linea "+linea);
				}
			case 19:
				if (charActual=='='){
					Token t = new Token(TK_DIST,"!=",linea);					
					estado = 0;
					return t;
				}
				else
				{
					stream.reset();
					Token t = new Token(TK_NOT,"!",linea);					
					estado = 0;
					return t;
				}
			case 20:
				if (charActual=='+' || charActual=='=')
					throw new LexicalException("Caracter extra�o  ! linea "+linea);
				else{
					stream.reset();
					Token t = new Token(TK_MAS,"+",linea);
					estado = 0;
					return t;
				}
			case 21:
				if (charActual=='-' || charActual=='=')
					throw new LexicalException("Caracter extra�o  ! linea "+linea);
				else{
					stream.reset();
					Token t = new Token(TK_MENOS,"-",linea);
					estado = 0;
					return t;
				}
			case 22:
				if (charActual=='=')
					throw new LexicalException("Caracter extra�o  ! linea "+linea);
				else{
					stream.reset();
					Token t = new Token(TK_POR,"*",linea);
					estado = 0;
					return t;
				}				
			case 24:
				if (charActual=='=')
					throw new LexicalException("Caracter extra�o  ! linea "+linea);
				else{
					stream.reset();
					Token t = new Token(TK_MOD,"%",linea);
					estado = 0;
					return t;
				}
			//default: no se correspondio a ningun estado -> lexema incorrecto
			default:
				throw new LexicalException("ERROR!:Secuencia de s�mbolos mal formado en linea "+linea);	
			}
			
		}
	}
	
	//---FUNCIONES AUXILIARES---
	
	/*
	 * Funci�n que chequea que un caracter dado sea una letra (min�scula o may�scula)
	 */
	private boolean esLetra(char car){
		return (car >= 'a' && car <= 'z') || (car >= 'A' && car <= 'Z')
				|| car == '�' || car == '�' || car == '�' || car == '�'
		|| car == '�' || car == '�' || car == '�' || car == '�' || car == '�' ||
		car == '�' || car == '�' || car == '�' || car == '�' || car == '�';
	}
	
	/*
	 * Funci�n que chequea si un car�cter puede ser considerado como "terminador"
	 * (en el caso de un n�mero, si �ste car�cter marca el final del mismo)
	 */
	private boolean esTerminador(char car){
		return car == '+' || car == '*' || car == '-' || car == '/' || car == '%' ||
			   car == ')' || car == '}' || car == ' ' || car == '\n' ||
			   car == '\t' || car == '.' || car == ';' || car == '\r' ||
			   car == '<' || car == '>' || car == '=' || car == '|' || car == '&';		
	}
	/*
	 * Funci�n que chequea que un caracter dado sea un d�gito
	 */
	private boolean esNum(char caracter){
		return (caracter >= '0' && caracter <= '9');
	}
	
	/*
	 *  Funci�n que chequea que un caracter es extra�o (?, ++,--)
	 */
	private boolean esCharExtraño(char ch){
		return (ch=='~'||ch=='?'|| ch=='�'||ch==':'||ch=='^'||ch=='['||ch==']'||
				ch=='#' || ch=='$' || ch=='$' || ch=='�' || ch=='@');
	}
	/*
	 * Inicializador de la tabla hash de palabras reservadas
	 */
	private void inicializarPalabraReservada(){
		
		palabrasReservadas.put("class",TK_CLASS);
		palabrasReservadas.put("if",TK_IF);
		palabrasReservadas.put("else",TK_ELSE);
		palabrasReservadas.put("extends",TK_EXTENDS);
		palabrasReservadas.put("new",TK_NEW);
		palabrasReservadas.put("return",TK_RETURN);
		palabrasReservadas.put("super",TK_SUPER);
		palabrasReservadas.put("this",TK_THIS);
		palabrasReservadas.put("while",TK_WHILE);
		palabrasReservadas.put("boolean",TK_BOOLEAN);
		palabrasReservadas.put("char",TK_CHAR);
		palabrasReservadas.put("int",TK_INT);
		palabrasReservadas.put("for",TK_FOR);
		palabrasReservadas.put("void",TK_VOID);
		palabrasReservadas.put("String",TK_STRING);
		palabrasReservadas.put("classDef",TK_CLASSDEF);
		palabrasReservadas.put("true",TK_TRUE);
		palabrasReservadas.put("false",TK_FALSE);
		palabrasReservadas.put("null",TK_NULL);		
	}
	/*
	 * Inicializador de la tabla hash de palabras prohibidas
	 */
	private void inicializarPalabraProhibidas(){
		palabrasProhibidas.put("abstract","abstract");
		palabrasProhibidas.put("default","default");
		palabrasProhibidas.put("long","long");
		palabrasProhibidas.put("swich","swich");
		palabrasProhibidas.put("try","try");
		palabrasProhibidas.put("byte","byte");
		palabrasProhibidas.put("do","do");
		palabrasProhibidas.put("implements","implements");
		palabrasProhibidas.put("native","native");
		palabrasProhibidas.put("synchronized","synchronized");
		palabrasProhibidas.put("volatile","volatile");
		palabrasProhibidas.put("case","case");
		palabrasProhibidas.put("double","double");
		palabrasProhibidas.put("import","import");
		palabrasProhibidas.put("goto","goto");
		palabrasProhibidas.put("throw","throw");
		palabrasProhibidas.put("catch","catch");
		palabrasProhibidas.put("final","final");
		palabrasProhibidas.put("instanceof","instanceof");
		palabrasProhibidas.put("package","package");
		palabrasProhibidas.put("throws","throws");
		palabrasProhibidas.put("const","const");
		palabrasProhibidas.put("finally","finally");
		palabrasProhibidas.put("interface","interface");
		palabrasProhibidas.put("short","short");
		palabrasProhibidas.put("transient","transient");
		palabrasProhibidas.put("break","break");
		palabrasProhibidas.put("continue","continue");
		palabrasProhibidas.put("private","private");
		palabrasProhibidas.put("protected","protected");
		palabrasProhibidas.put("public","public");
		palabrasProhibidas.put("static","static");
		palabrasProhibidas.put("byvalue","byvalue");
		palabrasProhibidas.put("none","none");
		palabrasProhibidas.put("cast","cast");
		palabrasProhibidas.put("operator","operator");
		palabrasProhibidas.put("future","future");
		palabrasProhibidas.put("outer","outer");
		palabrasProhibidas.put("generic","generic");
		palabrasProhibidas.put("rest","rest");
		palabrasProhibidas.put("inner","inner");
		palabrasProhibidas.put("var","var");
		palabrasProhibidas.put("float","float");		
	}	
}


